﻿using System;
using System.Collections.Generic;
using System.Reflection;
using Td.Additional.ComponentModel;

namespace Td.Additional.Xml
{
    internal class XmlReadPropertiesProcess
    {
        internal List<Object> ReferenceObjects = new List<object>();

        internal List<XmlObjectInfo> Objects = new List<XmlObjectInfo>();

        internal List<Exception> Exceptions = new List<Exception>();

        /// <summary>
        /// Adds a new reference object.
        /// </summary>
        /// <param name="obj">The obj.</param>
        internal void AddReferenceObject(Object obj)
        {
            ReferenceObjects.Add(obj);
            Type objType = obj.GetType();
            object[] attr = objType.GetCustomAttributes(typeof(IdAttribute), true);
            if (attr.Length > 0)
            {
                var idStr = (attr[0] as IdAttribute).IdPropertyName;
                var id = objType.InvokeMember(idStr, System.Reflection.BindingFlags.GetProperty, null, obj, null).ToString();
                XmlObjectInfo objectInfo = null;
                while ((objectInfo = GetObjectInfo(objType, id)) != null)
                {
                    if (objectInfo.ContainerProperty.PropertyType.IsGenericType)
                    {
                        Type[] subTypes = objectInfo.ObjectType.GetGenericArguments();
                        object list = objectInfo.ObjectContainer.GetType().InvokeMember(objectInfo.ContainerProperty.Name, BindingFlags.GetProperty, null, objectInfo.ObjectContainer, null);
                        list.GetType().InvokeMember("Add", BindingFlags.InvokeMethod, null, list, new object[] { obj });
                    }
                    else
                        objectInfo.ObjectContainer.GetType().InvokeMember(objectInfo.ContainerProperty.Name, BindingFlags.SetProperty, null, objectInfo.ObjectContainer, new object[] { obj });
                    Objects.Remove(objectInfo);
                }
            }
        }

        /// <summary>
        /// Adds an object to be replaced by a reference object.
        /// </summary>
        /// <param name="objectInfo">The object info.</param>
        /// <returns></returns>
        internal Object AddObject(XmlObjectInfo objectInfo)
        {
            // TODO: Check, if types and attributes are correctly assigned
            foreach (var referenceObject in ReferenceObjects)
            {
                var refType = referenceObject.GetType();
                object[] refAttr = refType.GetCustomAttributes(typeof(IdAttribute), true);
                if (refAttr.Length > 0)
                {
                    var refIdStr = (refAttr[0] as IdAttribute).IdPropertyName;
                    var id = refType.InvokeMember(refIdStr, System.Reflection.BindingFlags.GetProperty, null, referenceObject, null).ToString();
                    if (objectInfo.ObjectId.Equals(id) && objectInfo.ObjectType.Equals(refType))
                    {
                        if (objectInfo.ContainerProperty.PropertyType.IsGenericType)
                        {
                            object list = objectInfo.ObjectContainer.GetType().InvokeMember(objectInfo.ContainerProperty.Name, BindingFlags.GetProperty, null, objectInfo.ObjectContainer, null);
                            list.GetType().InvokeMember("Add", BindingFlags.InvokeMethod, null, list, new object[] { referenceObject });
                        }
                        else
                            objectInfo.ObjectContainer.GetType().InvokeMember(objectInfo.ContainerProperty.Name, BindingFlags.SetProperty, null, objectInfo.ObjectContainer, new object[] { referenceObject });
                        return referenceObject;
                    }
                }
            }
            Objects.Add(objectInfo);
            return null;
        }

        internal XmlObjectInfo GetObjectInfo(Type type, String id)
        {
            foreach (var objectInfo in Objects)
                if (objectInfo.ObjectType.Equals(type) && objectInfo.ObjectId.Equals(id))
                    return objectInfo;
            return null;
        }
    }
}